/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "runtime/profiling/profiling.h"
#include "plugins/ecmascript/runtime/compiler/ecmascript_runtime_interface.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/global_dictionary-inl.h"
#include "plugins/ecmascript/compiler/ecmascript_extensions/ecmascript_environment.h"
#include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h"
#include "serializer/serializer.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/js_hclass-inl.h"
#include "plugins/ecmascript/runtime/ic/ic_handler.h"
#include "plugins/ecmascript/runtime/ic/profile_type_info.h"
#include "plugins/ecmascript/runtime/ic/proto_change_details.h"
#include "runtime/mem/refstorage/global_object_storage.h"

namespace ark::ecmascript {

EcmaRuntimeInterface::EcmaRuntimeInterface(const EcmaVM *ecmaVm, mem::InternalAllocatorPtr internalAllocator)
    : ecmaVm_(ecmaVm), internalAllocator_(internalAllocator)
{
}
EcmaRuntimeInterface::~EcmaRuntimeInterface()
{
    os::memory::LockHolder lock(mutexH_);
    for (auto it : handlesByMethodTable_) {
        internalAllocator_->template Delete(it.second);
    }
}

size_t EcmaRuntimeInterface::GetLanguageExtensionSize([[maybe_unused]] Arch arch) const
{
    return cross_values::GetEcmascriptEnvironmentSize(arch);
}

std::string EcmaRuntimeInterface::GetMethodFullName(MethodPtr method, [[maybe_unused]] bool withSignature) const
{
    ASSERT(ark::panda_file::IsDynamicLanguage(MethodCast(method)->GetClass()->GetSourceLang()));
    return std::string(static_cast<ecmascript::JSMethod *>(MethodCast(method))->GetFullName());
}

uint64_t EcmaRuntimeInterface::DynamicCastDoubleToInt(double value, size_t bits) const
{
    return ecmascript::base::NumberHelper::DoubleToInt(value, bits);
}

uint8_t EcmaRuntimeInterface::GetDynamicNumFixedArgs() const
{
    return 3;  // func, new_target, this
}

uint32_t EcmaRuntimeInterface::GetFunctionTargetOffset([[maybe_unused]] Arch arch) const
{
    return cross_values::GetJsfunctionMethodOffset(arch);
}

uintptr_t EcmaRuntimeInterface::GetGlobalVarAddress(MethodPtr method, size_t id)
{
    auto thread = TryGetJSThread();
    // Thread can be removed during destroy runtime
    if (thread == nullptr) {
        return 0;
    }

    ScopedMutatorLock lock;

    auto [dict, entry] = GetGlobalDictionaryEntry(thread, method, id);
    if (entry == -1) {
        return 0;
    }

    JSTaggedValue res(dict->GetSafeBox(entry));
    // we can't set an attribute in compiler thread.
    if (res.IsUndefined() || res.IsHole() || !res.IsPropertyBox()) {
        return 0;
    }

    ASSERT(res.IsPropertyBox());
    return reinterpret_cast<uintptr_t>(res.GetHeapObject());
}

#include "plugins/ecmascript/runtime/builtins/generated/builtins_resolve_inlinable_gen.inl"

EcmaRuntimeInterface::MethodProfile EcmaRuntimeInterface::GetMethodProfile(MethodPtr method, bool fromVector) const
{
    if (fromVector) {
        if (auto profVector = JsMethodCast(method)->GetProfilingVector(); profVector != nullptr) {
            return reinterpret_cast<EcmaRuntimeInterface::MethodProfile>(profVector);
        }
        return profiling::INVALID_PROFILE;
    }
    auto name = GetMethodFullName(method, false);
    auto prof = profile_.find(std::string(name));
    if (prof == profile_.end()) {
        LOG(DEBUG, COMPILER) << "Profile not found for " << name;
    }
    return (prof != profile_.end()) ? reinterpret_cast<EcmaRuntimeInterface::MethodProfile>(
                                          const_cast<EcmaProfileElement::value_type *>(prof->second.data()))
                                    : profiling::INVALID_PROFILE;
}

EcmaRuntimeInterface::BytecodeProfile EcmaRuntimeInterface::GetBytecodeProfile(MethodProfile profile,
                                                                               const uint8_t *bcInst,
                                                                               [[maybe_unused]] size_t pc) const
{
    if (profile == nullptr) {
        return 0;
    }
    auto profileId = BytecodeInstruction(bcInst).GetProfileId();
    if (profileId == -1) {
        return 0;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    return reinterpret_cast<uintptr_t>(reinterpret_cast<uint8_t *>(profile) + profileId);
}

bool EcmaRuntimeInterface::CanInlineLdStObjByIndex(const BytecodeInstruction *bcInst, size_t pc,
                                                   MethodProfile methodProfile) const
{
    auto profile = GetBytecodeProfile(methodProfile, bcInst->GetAddress(), pc);
    auto ecmaProf = reinterpret_cast<uint8_t *>(profile);
    ark::ecmascript::ObjByIndexOperationProfile p(ecmaProf);
    return p.GetOperandType(0).GetType() == ark::ecmascript::ProfilingIndexedAccessBits::OBJECT_ARRAY_ACCESS;
}

profiling::CallKind EcmaRuntimeInterface::GetCallProfile(MethodPtr method, uint32_t pc, ArenaVector<uintptr_t> *methods,
                                                         bool isAot)
{
    auto profile = GetMethodProfile(method, !isAot);
    if (profile == nullptr) {
        return profiling::CallKind::UNKNOWN;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    auto bcInst = MethodCast(method)->GetInstructions() + pc;
    auto profileId = BytecodeInstruction(bcInst).GetProfileId();
    if (profileId == -1) {
        return profiling::CallKind::UNKNOWN;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    auto callProf = CallProfile::FromBuffer(reinterpret_cast<uint8_t *>(profile) + profileId);
    auto callKind = callProf->GetCallKind();
    if (callKind != profiling::CallKind::UNKNOWN && callKind != profiling::CallKind::MEGAMORPHIC) {
        for (auto ptr : callProf->GetCalleesPtr(ecmaVm_->GetEcmaCallProfileTable())) {
            if (ptr == CallProfile::UNKNOWN) {
                break;
            }
            methods->push_back(ptr);
        }
    }
    return callKind;
}

Expected<bool, const char *> EcmaRuntimeInterface::AddProfile(std::string_view fname)
{
    std::ifstream stm(fname.data(), std::ios::binary);
    if (!stm.is_open()) {
        return Unexpected("Cannot open profile file");
    }

    std::vector<uint8_t> buffer {std::istreambuf_iterator<char>(stm), std::istreambuf_iterator<char>()};
    auto res = serializer::BufferToType(buffer.data(), buffer.size(), profile_);
    if (!res) {
        LOG(FATAL, RUNTIME) << "Failed to deserialize: " << res.Error();
    }

    return true;
}

inline compiler::AnyBaseType GetTypeOfType(ark::ecmascript::ProfilingTypeOfBits::Type type,
                                           profiling::AnyInputType *allowedInputType, bool *isTypeProfiled)
{
    if (type == ark::ecmascript::ProfilingTypeOfBits::NONE) {
        return compiler::AnyBaseType::UNDEFINED_TYPE;
    }

    *isTypeProfiled = true;
    switch (type) {
        case ark::ecmascript::ProfilingTypeOfBits::NUMBER: {
            *allowedInputType = profiling::AnyInputType::INTEGER;
            return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE;
        }
        case ark::ecmascript::ProfilingTypeOfBits::SYMBOL:
            return compiler::AnyBaseType::ECMASCRIPT_SYMBOL_TYPE;
        case ark::ecmascript::ProfilingTypeOfBits::BOOLEAN:
            return compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE;
        case ark::ecmascript::ProfilingTypeOfBits::STRING:
            return compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE;
        case ark::ecmascript::ProfilingTypeOfBits::FUNCTION:
            return compiler::AnyBaseType::ECMASCRIPT_CALLABLE_TYPE;
        case ark::ecmascript::ProfilingTypeOfBits::UNDEFINED:
            return compiler::AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE;
        case ark::ecmascript::ProfilingTypeOfBits::BIGINT:
            return compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE;
        default:
            return compiler::AnyBaseType::UNDEFINED_TYPE;
    }
    return compiler::AnyBaseType::UNDEFINED_TYPE;
}

static bool IsEqualsInst(const BytecodeInstruction *bcInst)
{
    switch (bcInst->GetOpcode()) {
        case BytecodeInstruction::Opcode::ECMA_EQDYN_PREF_V8_PROF16:
        case BytecodeInstruction::Opcode::ECMA_NOTEQDYN_PREF_V8_PROF16:
        case BytecodeInstruction::Opcode::ECMA_STRICTEQDYN_PREF_V8_PROF16:
        case BytecodeInstruction::Opcode::ECMA_STRICTNOTEQDYN_PREF_V8_PROF16:
            return true;
        default:
            return false;
    }
}

compiler::AnyBaseType EcmaRuntimeInterface::GetProfilingAnyType(RuntimeInterface::BytecodeProfile profile,
                                                                const BytecodeInstruction *bcInst, unsigned index,
                                                                profiling::AnyInputType *allowedInputType,
                                                                bool *isTypeProfiled)
{
    auto kind = profiling::GetProfileKind(bcInst->GetOpcode());
    auto ecmaProf = reinterpret_cast<uint8_t *>(profile);
    ProfilingTypeBits::Type type = ProfilingTypeBits::NONE;
    switch (kind) {
        case profiling::ProfilingKind::TYPE_OF: {
            ark::ecmascript::TypeOfOperationProfile p(ecmaProf);
            return GetTypeOfType(p.GetOperandType(index).GetType(), allowedInputType, isTypeProfiled);
        }
        case profiling::ProfilingKind::UNARY_ARITH: {
            UnaryOperationProfile p(ecmaProf);
            type = p.GetOperandType(index).GetType();
            if (type != ProfilingTypeBits::NONE) {
                *isTypeProfiled = true;
            }

            if (type == ProfilingTypeBits::STRING) {
                return compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE;
            }
            if ((type & ProfilingTypeBits::HEAP_OBJECT) != 0) {
                return compiler::AnyBaseType::UNDEFINED_TYPE;
            }
            break;
        }
        case profiling::ProfilingKind::BINARY_ARITH: {
            BinaryOperationProfile p(ecmaProf);
            type = p.GetOperandType(index).GetType();
            if (type != ProfilingTypeBits::NONE) {
                *isTypeProfiled = true;
            }
            auto otherOperandType = p.GetOperandType(1 - index).GetType();
            if (type == ProfilingTypeBits::STRING && otherOperandType == ProfilingTypeBits::STRING) {
                return compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE;
            }
            auto objectCount = ((type & ProfilingTypeBits::HEAP_OBJECT) != 0 ? 1 : 0) +
                               ((otherOperandType & ProfilingTypeBits::HEAP_OBJECT) != 0 ? 1 : 0);
            // For opcodes like eq non-numeric types are common, do not inline them if object was seen as one of inputs
            // For arithmetic opcodes if we saw double and object, assume double type and deoptimize without method
            // destruction in case of object
            if (objectCount == 2 || (objectCount == 1 && IsEqualsInst(bcInst))) {
                *isTypeProfiled = true;
                return compiler::AnyBaseType::UNDEFINED_TYPE;
            }
            break;
        }
        default:
            LOG(FATAL, COMMON) << "Unknown profile";
    }

    if (type == ProfilingTypeBits::NONE) {
        return compiler::AnyBaseType::UNDEFINED_TYPE;
    }

    if (type == ProfilingTypeBits::BOOLEAN) {
        return compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE;
    }
    auto acceptSpecial = (type & ProfilingTypeBits::SPECIAL) != 0 || (type & ProfilingTypeBits::BOOLEAN) != 0;
    if (acceptSpecial && IsEqualsInst(bcInst)) {
        // some instructions treat NaN and undefined or true and 1 differently
        return compiler::AnyBaseType::UNDEFINED_TYPE;
    }
    if ((type & ProfilingTypeBits::SPECIAL_INT) == type) {
        if (acceptSpecial) {
            *allowedInputType = profiling::AnyInputType::SPECIAL;
        }
        return compiler::AnyBaseType::ECMASCRIPT_INT_TYPE;
    }

    // If object was seen only in one of inputs and we have seen a number as input,
    // build AnyTypeCheck with Double type for this input
    if ((type & ProfilingTypeBits::DOUBLE_INTEGER) != 0) {
        uint8_t allowedTypeMask = 0;
        if (acceptSpecial) {
            allowedTypeMask |= profiling::AnyInputType::SPECIAL;
        }
        if ((type & ProfilingTypeBits::INTEGER) != 0) {
            allowedTypeMask |= profiling::AnyInputType::INTEGER;
        }
        *allowedInputType = static_cast<profiling::AnyInputType>(allowedTypeMask);
        return compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE;
    }

    return compiler::AnyBaseType::UNDEFINED_TYPE;
}

compiler::AnyBaseType EcmaRuntimeInterface::ResolveSpecialAnyTypeByConstant(coretypes::TaggedValue anyConst)
{
    JSTaggedValue jsAnyConst(anyConst);
    ASSERT(jsAnyConst.IsSpecial());
    if (jsAnyConst == JSTaggedValue::Hole()) {
        return compiler::AnyBaseType::ECMASCRIPT_HOLE_TYPE;
    }
    if (jsAnyConst == JSTaggedValue::Undefined()) {
        return compiler::AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE;
    }
    if ((jsAnyConst == JSTaggedValue::False()) || (jsAnyConst == JSTaggedValue::True())) {
        return compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE;
    }
    if (jsAnyConst == JSTaggedValue::Null()) {
        return compiler::AnyBaseType::ECMASCRIPT_NULL_TYPE;
    }
    return compiler::AnyBaseType::UNDEFINED_TYPE;
}

size_t EcmaRuntimeInterface::GetGlobalConstStringOffsetForAnyType(compiler::AnyBaseType type, Arch arch) const
{
    auto globalConstArrayOffset =
        cross_values::GetJsthreadGlobalConstantsOffset(arch) + cross_values::GetGlobalConstConstantsOffset(arch);
    switch (type) {
        case compiler::AnyBaseType::ECMASCRIPT_UNDEFINED_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::UNDEFINED_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_STRING_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::STRING_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_INT_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::NUMBER_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_DOUBLE_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::NUMBER_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_SYMBOL_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::SYMBOL_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_CALLABLE_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::FUNCTION_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_BOOLEAN_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::BOOLEAN_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_NULL_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::OBJECT_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_BIGINT_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::BIGINT_STRING_INDEX);
        case compiler::AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE:
            return globalConstArrayOffset + ark::ecmascript::GlobalEnvConstants::GetGlobalConstantOffset(
                                                ark::ecmascript::ConstantIndex::OBJECT_STRING_INDEX);
        default:
            UNREACHABLE();
    }
    return 0;
}
RuntimeInterface::NewObjDynInfo EcmaRuntimeInterface::GetNewObjDynInfo(uintptr_t ctor) const
{
    static constexpr NewObjDynInfo SLOW_PATH = {NewObjDynInfo::AllocatorType::SLOW_PATH,
                                                NewObjDynInfo::ResolverType::RESOLVE};
    [[maybe_unused]] ScopedMutatorLock lock;
    JSObject *ctorPtr = *reinterpret_cast<JSObject **>(ctor);
    if (ctorPtr == nullptr || !ctorPtr->IsJSFunction()) {
        return SLOW_PATH;
    }
    auto jsFun = static_cast<JSFunction *>(ctorPtr);
    if (!jsFun->IsConstructor()) {
        return SLOW_PATH;
    }

    auto method = jsFun->GetMethod();

    if (method->IsNative()) {
        if (jsFun->IsBuiltinConstructor()) {
            return {NewObjDynInfo::AllocatorType::UNDEFINED, NewObjDynInfo::ResolverType::USE_CTOR_RESULT};
        }
        return SLOW_PATH;
    }
    if (!jsFun->IsBase()) {
        if (jsFun->IsDerivedConstructor()) {
            return {NewObjDynInfo::AllocatorType::UNDEFINED, NewObjDynInfo::ResolverType::RESOLVE};
        }
        return SLOW_PATH;
    }
    return {NewObjDynInfo::AllocatorType::ALLOC_OBJ, NewObjDynInfo::ResolverType::RESOLVE};
}

bool EcmaRuntimeInterface::AddProfileInfo(PandaRuntimeInterface::MethodPtr m,
                                          ArenaVector<NamedAccessProfileData> *profile,
                                          ProfileTypeInfo *profileTypeInfo, uint8_t slot, uintptr_t key)
{
    JSTaggedValue hclassValue = profileTypeInfo->Get(slot);
    if (!hclassValue.IsHeapObject()) {
        return false;
    }
    auto klass = static_cast<JSHClass *>(hclassValue.GetHeapObject())->GetHClass();
    JSTaggedValue handler = profileTypeInfo->Get(slot + 1);
    if (UNLIKELY(handler.IsUndefined())) {
        return false;
    }
    auto method = MethodCast(m);
    if (LIKELY(handler.IsInt())) {
        auto handlerInfo = static_cast<uint32_t>(handler.GetInt());
        if (LIKELY(HandlerBase::IsField(handlerInfo))) {
            if (HandlerBase::IsArrayOverflowed(handlerInfo)) {
                return false;
            }
            int index = HandlerBase::GetOffset(handlerInfo);
            bool isInlined = HandlerBase::IsInlinedProps(handlerInfo);
            NamedAccessProfileType type =
                isInlined ? NamedAccessProfileType::FIELD_INLINED : NamedAccessProfileType::FIELD;
            auto offset = isInlined ? static_cast<uint32_t>(index * JSTaggedValue::TaggedTypeSize())
                                    : static_cast<uint32_t>(index);
            AddObjectHandle(method, hclassValue.GetHeapObject());
            profile->push_back({klass, 0, key, offset, type});
            return true;
        }
        return false;
    }
    if (handler.IsTransitionHandler()) {
        TransitionHandler *transitionHandler = TransitionHandler::Cast(handler.GetTaggedObject());
        JSHClass *newHClass = JSHClass::Cast(transitionHandler->GetTransitionHClass().GetTaggedObject());
        uint32_t handlerInfo = transitionHandler->GetHandlerInfo().GetInt();
        ASSERT(HandlerBase::IsField(handlerInfo));
        if (HandlerBase::IsArrayOverflowed(handlerInfo)) {
            return false;
        }
        int index = HandlerBase::GetOffset(handlerInfo);
        bool isInlined = HandlerBase::IsInlinedProps(handlerInfo);
        NamedAccessProfileType type =
            isInlined ? NamedAccessProfileType::TRANSITION_INLINED : NamedAccessProfileType::TRANSITION;
        auto offset =
            isInlined ? static_cast<uint32_t>(index * JSTaggedValue::TaggedTypeSize()) : static_cast<uint32_t>(index);
        AddObjectHandle(method, hclassValue.GetHeapObject());
        AddObjectHandle(method, newHClass);
        profile->push_back({klass, reinterpret_cast<uintptr_t>(newHClass->GetHClass()), key, offset, type});
        return true;
    }
    if (handler.IsPrototypeHandler()) {
        PrototypeHandler *prototypeHandler = PrototypeHandler::Cast(handler.GetTaggedObject());
        auto cellValue = prototypeHandler->GetProtoCell();
        if (cellValue.IsFalse()) {
            // For access to undefined property of object without prototype tagged false is stored to proto cell
            return false;
        }
        ASSERT(cellValue.IsProtoChangeMarker());
        ProtoChangeMarker *cell = ProtoChangeMarker::Cast(cellValue.GetHeapObject());
        if (cell->GetHasChanged()) {
            return false;
        }
        JSTaggedValue handlerValue = prototypeHandler->GetHandlerInfo();
        if (!handlerValue.IsInt()) {
            return false;
        }
        uint32_t handlerInfo = handlerValue.GetInt();
        if (!HandlerBase::IsField(handlerInfo)) {
            return false;
        }
        int index = HandlerBase::GetOffset(handlerInfo);
        bool isInlined = HandlerBase::IsInlinedProps(handlerInfo);
        NamedAccessProfileType type =
            isInlined ? NamedAccessProfileType::PROTOTYPE_INLINED : NamedAccessProfileType::PROTOTYPE;
        auto offset =
            isInlined ? static_cast<uint32_t>(index * JSTaggedValue::TaggedTypeSize()) : static_cast<uint32_t>(index);
        auto refAddr = AddFixedObjectHandle(method, handler.GetHeapObject());
        if (refAddr == 0) {
            return false;
        }
        AddObjectHandle(method, hclassValue.GetHeapObject());
        profile->push_back({klass, refAddr, key, offset, type});
        return true;
    }
    return false;
}

bool EcmaRuntimeInterface::AddElementInfo(ArenaVector<NamedAccessProfileData> *profile,
                                          ProfileTypeInfo *profileTypeInfo, uint8_t slot)
{
    JSTaggedValue hclassValue = profileTypeInfo->Get(slot);
    if (!hclassValue.IsHeapObject()) {
        return false;
    }
    ASSERT(hclassValue.IsJSHClass());
    auto klass = static_cast<JSHClass *>(hclassValue.GetHeapObject())->GetHClass();
    JSTaggedValue handler = profileTypeInfo->Get(slot + 1);
    if (UNLIKELY(handler.IsUndefined())) {
        return false;
    }
    ASSERT(handler.IsInt());
    auto handlerInfo = static_cast<uint32_t>(handler.GetInt());
    // NOTE(pishin) support for double
    if (UNLIKELY(!HandlerBase::IsKeyInt(handlerInfo))) {
        return false;
    }
    if (HandlerBase::IsArrayOverflowed(handlerInfo)) {
        return false;
    }

    if (HandlerBase::IsJSArray(handlerInfo)) {
        profile->push_back({klass, 0, 0, 0, NamedAccessProfileType::ARRAY_ELEMENT});
    } else {
        profile->push_back({klass, 0, 0, 0, NamedAccessProfileType::ELEMENT});
    }
    return true;
}

bool EcmaRuntimeInterface::AddProfileValueInfo(PandaRuntimeInterface::MethodPtr m,
                                               ArenaVector<NamedAccessProfileData> *profile,
                                               ProfileTypeInfo *profileTypeInfo, uint8_t slot)
{
    JSTaggedValue firstValue = profileTypeInfo->Get(slot);
    if (!firstValue.IsHeapObject()) {
        return false;
    }
    if (firstValue.IsJSHClass()) {
        if (AddElementInfo(profile, profileTypeInfo, slot)) {
            AddElementInfo(profile, profileTypeInfo, slot + 2);
            return true;
        }
        return false;
    }
    auto refAddr = AddFixedObjectHandle(MethodCast(m), firstValue.GetHeapObject());
    if (refAddr == 0) {
        return false;
    }
    return AddProfileInfo(m, profile, profileTypeInfo, slot + 1, refAddr);
}

inline ProfileTypeInfo *EcmaRuntimeInterface::GetProfileTypeInfo(PandaRuntimeInterface::MethodPtr m, uintptr_t slotId,
                                                                 uint8_t *slot) const
{
    // ProfileTypeInfo is manage object
    // We need to have lock in caller method, because we return ProfileTypeInfo
    ASSERT(ecmaVm_->GetMutatorLock()->HasLock());
    uint8_t *mapping = JsMethodCast(m)->GetICMapping();
    if (mapping == nullptr) {
        return nullptr;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    *slot = mapping[slotId];
    if (auto func = GetJSFunctionByMethod(m); func != nullptr) {
        auto pti = func->GetProfileTypeInfo();
        if (pti.IsHeapObject()) {
            return ProfileTypeInfo::Cast(pti.GetTaggedObject());
        }
    }
    return nullptr;
}

inline ProfileTypeInfo *EcmaRuntimeInterface::GetProfileTypeInfo(uintptr_t funcAddress, uintptr_t slotId,
                                                                 uint8_t *slot) const
{
    ASSERT(funcAddress != 0);
    // ProfileTypeInfo is manage object
    // We need to have lock in caller method, because we return ProfileTypeInfo
    ASSERT(ecmaVm_->GetMutatorLock()->HasLock());
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    auto jsFunc = TaggedValue(reinterpret_cast<uint64_t *>(funcAddress)[0]);
    if (!jsFunc.IsHeapObject()) {
        return nullptr;
    }
    auto func = JSFunction::Cast(jsFunc.GetHeapObject());
    uint8_t *mapping = func->GetMethod()->GetICMapping();
    if (mapping == nullptr) {
        return nullptr;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    *slot = mapping[slotId];
    auto profileInfoObj = func->GetProfileTypeInfo();
    if (profileInfoObj.IsUndefined()) {
        return nullptr;
    }

    return ProfileTypeInfo::Cast(profileInfoObj.GetTaggedObject());
}

template <typename Func>
bool EcmaRuntimeInterface::GetProfileDataForNamedAccessImpl(PandaRuntimeInterface::MethodPtr m, Func func,
                                                            uintptr_t slotId,
                                                            ArenaVector<NamedAccessProfileData> *profile)
{
    if (profile == nullptr) {
        return false;
    }
    profile->clear();
    ScopedMutatorLock lock;
    uint8_t slot;
    ProfileTypeInfo *profileTypeInfo = GetProfileTypeInfo(func, slotId, &slot);
    if (profileTypeInfo == nullptr) {
        return false;
    }
    if (AddProfileInfo(m, profile, profileTypeInfo, slot)) {
        AddProfileInfo(m, profile, profileTypeInfo, slot + 2);
        return true;
    }
    return false;
}

template <typename Func>
bool EcmaRuntimeInterface::GetProfileDataForValueAccessImpl(PandaRuntimeInterface::MethodPtr m, Func func,
                                                            uintptr_t slotId,
                                                            ArenaVector<NamedAccessProfileData> *profile)
{
    if (profile == nullptr) {
        return false;
    }
    profile->clear();
    ScopedMutatorLock lock;
    uint8_t slot;
    ProfileTypeInfo *profileTypeInfo = GetProfileTypeInfo(func, slotId, &slot);
    if (profileTypeInfo == nullptr) {
        return false;
    }

    return AddProfileValueInfo(m, profile, profileTypeInfo, slot);
}

void EcmaRuntimeInterface::CleanFunction(Method *method)
{
    os::memory::LockHolder lock(mutex_);

    auto it = jsFunctionTable_.find(method);
    if (it == jsFunctionTable_.end()) {
        return;
    }
    auto func = it->second;
    jsFunctionTable_.erase(it);
    ASSERT(ecmaVm_ != nullptr);
    ecmaVm_->GetGlobalObjectStorage()->Remove(func);
}

size_t EcmaRuntimeInterface::GetLexicalEnvParentEnvIndex() const
{
    return LexicalEnv::PARENT_ENV_INDEX;
}

size_t EcmaRuntimeInterface::GetLexicalEnvStartDataIndex() const
{
    return LexicalEnv::RESERVED_ENV_LENGTH;
}

size_t EcmaRuntimeInterface::GetTaggedArrayElementSize() const
{
    return JSTaggedValue::TaggedTypeSize();
}

void EcmaRuntimeInterface::CleanObjectHandles(Method *method)
{
    os::memory::LockHolder lock(mutexH_);
    auto it = handlesByMethodTable_.find(method);
    auto gos = ecmaVm_->GetGlobalObjectStorage();
    if (it == handlesByMethodTable_.end()) {
        return;
    }
    auto vector = it->second;
    // Find handles for the method
    for (auto *ref : *vector) {
        gos->Remove(ref);
    }
    handlesByMethodTable_.erase(it);
    internalAllocator_->template Delete(vector);
}

void EcmaRuntimeInterface::AddObjectHandle(Method *method, ObjectHeader *obj)
{
    os::memory::LockHolder lock(mutexH_);
    auto it = handlesByMethodTable_.find(method);
    auto gos = ecmaVm_->GetGlobalObjectStorage();
    // Create new Vector for the method
    if (it == handlesByMethodTable_.end()) {
        auto vector = internalAllocator_->template New<PandaVector<ark::mem::Reference *>>();
        auto funcRef = gos->Add(obj, ark::mem::Reference::ObjectType::GLOBAL);
        vector->push_back(funcRef);
        handlesByMethodTable_.insert({method, vector});
        return;
    }
    auto vector = it->second;
    // Find the object in the vector
    for (auto *ref : *vector) {
        if (obj == gos->Get(ref)) {
            return;
        }
    }
    auto funcRef = gos->Add(obj, ark::mem::Reference::ObjectType::GLOBAL);
    vector->push_back(funcRef);
}

uintptr_t EcmaRuntimeInterface::AddFixedObjectHandle(Method *method, ObjectHeader *obj)
{
    os::memory::LockHolder lock(mutexH_);
    auto it = handlesByMethodTable_.find(method);
    auto gos = ecmaVm_->GetGlobalObjectStorage();
    // Create new Vector for the method
    if (it == handlesByMethodTable_.end()) {
        auto vector = internalAllocator_->template New<PandaVector<ark::mem::Reference *>>();
        auto funcRef = gos->Add(obj, ark::mem::Reference::ObjectType::GLOBAL_FIXED);
        if (funcRef == nullptr) {
            return 0;
        }
        vector->push_back(funcRef);
        handlesByMethodTable_.insert({method, vector});
        return gos->GetAddressForRef(funcRef);
    }
    auto vector = it->second;
    // Find the object in the vector
    for (auto *ref : *vector) {
        if (obj == gos->Get(ref)) {
            return gos->GetAddressForRef(ref);
        }
    }
    auto funcRef = gos->Add(obj, ark::mem::Reference::ObjectType::GLOBAL_FIXED);
    if (funcRef == nullptr) {
        return 0;
    }
    vector->push_back(funcRef);
    return gos->GetAddressForRef(funcRef);
}

template <typename Func>
PandaRuntimeInterface::GlobalVarInfo EcmaRuntimeInterface::GetGlobalVarInfoImpl(PandaRuntimeInterface::MethodPtr method,
                                                                                Func func, size_t id,
                                                                                uintptr_t slotId) const
{
    auto thread = TryGetJSThread();
    if (thread == nullptr) {
        return {};
    }
    ScopedMutatorLock lock;
    auto [dict, entry] = GetGlobalDictionaryEntry(thread, method, id);
    if (entry == -1) {
        return {};
    }
    PropertyAttributes attr = dict->GetAttributes(entry);
    if (attr.IsAccessor()) {
        return {};
    }
    if (!attr.IsConfigurable()) {
        if (!attr.IsWritable()) {
            auto value = dict->GetValue(entry);
            if (!value.IsHeapObject()) {
                return {GlobalVarInfo::Type::CONSTANT, value.GetRawData()};
            }
        }
        JSTaggedValue res(dict->GetSafeBox(entry));
        if (!res.IsHeapObject()) {
            return {};
        }

        ASSERT(res.IsPropertyBox());
        auto address = reinterpret_cast<uintptr_t>(res.GetHeapObject());
        return {GlobalVarInfo::Type::NON_CONFIGURABLE, address};
    }

    uint8_t slot;
    ProfileTypeInfo *profileTypeInfo = GetProfileTypeInfo(func, slotId, &slot);
    if (profileTypeInfo == nullptr) {
        return {};
    }
    JSTaggedValue handler = profileTypeInfo->Get(slot);
    if (!handler.IsHeapObject()) {
        return {};
    }
    if (PropertyBox::Cast(handler.GetHeapObject())->GetValue().IsHole()) {
        // box was invalidated
        return {};
    }
    auto address = reinterpret_cast<uintptr_t>(handler.GetHeapObject());
    return {GlobalVarInfo::Type::FIELD, address};
}

JSThread *EcmaRuntimeInterface::TryGetJSThread() const
{
    if (ecmaVm_ == nullptr) {
        return nullptr;
    }
    auto thread = ecmaVm_->GetJSThread();
    if (thread == nullptr || !thread->IsThreadAlive()) {
        return nullptr;
    }
    return thread;
}

std::pair<GlobalDictionary *, int> EcmaRuntimeInterface::GetGlobalDictionaryEntry(JSThread *thread, MethodPtr method,
                                                                                  size_t id) const
{
    auto pandaFile = JsMethodCast(method)->GetPandaFile();
    JSTaggedValue constantPool(JSTaggedValue::VALUE_HOLE);

    auto func = [&](Program *p) { constantPool = p->GetConstantPool(); };
    ecmaVm_->EnumerateProgram(func, pandaFile->GetFilename());
    if (constantPool.IsUndefined()) {
        return {nullptr, -1};
    }

    ASSERT(!constantPool.IsHole());

    JSTaggedValue key = ConstantPool::Cast(constantPool.GetHeapObject())->GetObjectFromCache(id);
    auto globalObj = thread->GetGlobalObject();
    auto jsObj = JSObject::Cast(globalObj.GetTaggedObject());
    TaggedArray *array = TaggedArray::Cast(jsObj->GetProperties().GetTaggedObject());
    if (array->GetLength() == 0) {
        return {nullptr, -1};
    }

    auto dict = GlobalDictionary::Cast(array);
    return {dict, dict->FindEntry(key)};
}

JSFunction *EcmaRuntimeInterface::GetJSFunctionByMethod(PandaRuntimeInterface::MethodPtr m) const
{
    // JSFunction is manage object
    // We need to have lock in caller method, because we return JSFunction
    ASSERT(ecmaVm_->GetMutatorLock()->HasLock());
    os::memory::LockHolder lock(mutex_);
    auto it = jsFunctionTable_.find(MethodCast(m));
    if (it == jsFunctionTable_.end()) {
        return nullptr;
    }
    auto func = JSFunction::Cast(ecmaVm_->GetGlobalObjectStorage()->Get(it->second));
    ASSERT(func->GetCallTarget() == MethodCast(m));
    return func;
}

PandaRuntimeInterface::MethodPtr EcmaRuntimeInterface::GetMethodByIdAndSaveJsFunction(
    PandaRuntimeInterface::MethodPtr parentMethod, MethodId id)
{
    ScopedMutatorLock lock;
    auto func = GetJSFunctionByMethod(parentMethod);
    if (func == nullptr) {
        return nullptr;
    }
    auto constpool = ConstantPool::Cast(func->GetConstantPool().GetHeapObject());
    auto target = JSFunction::Cast(constpool->GetObjectFromCache(id).GetHeapObject());
    auto method = target->GetCallTarget();

    // Store JsFunction for recursive static inlining and futher optimizations
    auto gos = ecmaVm_->GetGlobalObjectStorage();
    auto funcRef = gos->Add(JSTaggedValue(target).GetHeapObject(), ark::mem::Reference::ObjectType::GLOBAL);
    AddFunctionInMap(method, funcRef);

    return method;
}

void *EcmaRuntimeInterface::GetConstantPool(PandaRuntimeInterface::MethodPtr method)
{
    ScopedMutatorLock lock;
    auto func = GetJSFunctionByMethod(method);
    if (func == nullptr) {
        return nullptr;
    }
    return func->GetConstantPool().GetHeapObject();
}

void *EcmaRuntimeInterface::GetConstantPool(uintptr_t funcAddress)
{
    ASSERT(funcAddress != 0);
    ScopedMutatorLock lock;
    auto jsFunc = JSTaggedValue(*reinterpret_cast<JSTaggedType *>(funcAddress));
    return JSFunction::Cast(jsFunc.GetHeapObject())->GetConstantPool().GetHeapObject();
}

}  // namespace ark::ecmascript
